/*
 * RealmGuard.java
 *
 * Created on September 18, 2007, 2:55 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.atomojo.www.util;

import java.io.IOException;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import java.util.logging.Level;
import org.atomojo.app.client.XMLRepresentationParser;
import org.infoset.xml.Document;
import org.infoset.xml.Element;
import org.infoset.xml.XMLException;
import org.restlet.Client;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.ChallengeResponse;
import org.restlet.data.ChallengeScheme;
import org.restlet.data.Cookie;
import org.restlet.data.Method;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
import org.restlet.security.ChallengeAuthenticator;
import org.restlet.security.Verifier;

/**
 *
 * @author alex
 */
public class RealmGuard extends ChallengeAuthenticator
{
   
   Set<UUID> roles;
   Reference service;
   boolean allowSessions;
   boolean allowAuthentication;
   /** Creates a new instance of RealmGuard */
   public RealmGuard(Context context,ChallengeScheme scheme,String realm,Reference service)
   {
      super(context,scheme,realm);
      this.service = service;
      this.roles = new TreeSet<UUID>();
      this.allowSessions = false;
      this.allowAuthentication = true;
      getLogger().info("Using auth service "+service);

      setVerifier(new Verifier() {
         public int verify(Request request,Response reponse) {

            if (allowAuthentication) {
               ChallengeResponse cr = request.getChallengeResponse();

               if (cr != null) {
                  String identifier = cr.getIdentifier();
                  char[] secret = cr.getSecret();

                  // Check the credentials
                  if ((identifier != null) && (secret != null)) {
                     if (checkPassword(request,identifier, secret)) {
                        return authorize(request) ? Verifier.RESULT_VALID : Verifier.RESULT_INVALID;
                     } else {
                        return Verifier.RESULT_INVALID;
                     }
                  }
               }
            }
            if (allowSessions) {
               Cookie cookie = request.getCookies().getFirst("I");
               if (cookie!=null) {
                  try {
                     Identity identity = checkSession(getContext().createChildContext(),RealmGuard.this.service,cookie.getValue());
                     if (identity!=null) {
                        request.getAttributes().put(Identity.IDENTITY_ATTR,identity);
                        return Verifier.RESULT_VALID;
                     }
                  } catch (RequestFailedException ex) {
                     if (ex.getStatus()!=null) {
                        getLogger().severe("Cannot authenticate against service, status="+ex.getStatus().getCode());
                     } else {
                        getLogger().log(Level.SEVERE,"Cannot authenticate against service.",ex);
                     }
                  }
               }
            }

            return Verifier.RESULT_MISSING;
         }
      });
   }
   
   public void setAllowSessions(boolean flag)
   {
      allowSessions = flag;
   }
   
   public void setAllowAuthentication(boolean flag)
   {
      allowAuthentication = flag;
   }
   
   public Set<UUID> getRoles() {
      return roles;
   }
   
   public static Identity checkSession(Context context,Reference service,String session)
      throws RequestFailedException
   {
      Client client = context.getClientDispatcher();
      //Client client = new Client(context,service.getSchemeProtocol());
      //client.getContext().getAttributes().put("hostnameVerifier", org.apache.commons.ssl.HostnameVerifier.DEFAULT);
      Reference ref = new Reference(service+"/"+session);
      Request authRequest = new Request(Method.GET,ref);
      Response response = client.handle(authRequest);
      try {
         if (response.getStatus().isSuccess()) {
            try {
               return createIdentity(response.getEntity());
            } catch (Exception ex) {
               throw new RequestFailedException("Exception while processing result from session lookup.",ex);
            }
         } else if (response.getStatus().getCode()==Status.CLIENT_ERROR_UNAUTHORIZED.getCode()) {
            return null;
         } else {
            throw new RequestFailedException("Request to realm failed.",response.getStatus());
         }
      } finally {
         if (response.isEntityAvailable()) {
            response.getEntity().release();
         }
      }
   }
   
   public static Identity createIdentity(Representation entity)
      throws IOException,XMLException
   {
      XMLRepresentationParser parser = new XMLRepresentationParser();
      Document doc = parser.load(entity);
      String session = doc.getDocumentElement().getAttributeValue("id");
      String id = doc.getDocumentElement().getAttributeValue("user-id");
      String alias = doc.getDocumentElement().getAttributeValue("user-alias");
      Element nameE = doc.getDocumentElement().getFirstElementNamed(IdentityFilter.NAME);
      Element emailE = doc.getDocumentElement().getFirstElementNamed(IdentityFilter.EMAIL);
      Identity identity = new Identity(session,id,alias,nameE==null ? null : nameE.getText(),emailE==null ? null : emailE.getText());
      Iterator<Element> roles = doc.getDocumentElement().getElementsByName(IdentityFilter.ROLE);
      while (roles.hasNext()) {
         Element role = roles.next();
         UUID rid = UUID.fromString(role.getAttributeValue("id"));
         String name = role.getAttributeValue("name");
         if (identity.roles==null) {
            identity.roles = new TreeMap<UUID,String>();
         }
         identity.roles.put(rid,name);
      }
      Iterator<Element> groups = doc.getDocumentElement().getElementsByName(IdentityFilter.GROUP);
      while (groups.hasNext()) {
         Element group = groups.next();
         UUID gid = UUID.fromString(group.getAttributeValue("id"));
         String galias = group.getAttributeValue("alias");
         if (identity.groups==null) {
            identity.groups = new TreeMap<UUID,String>();
         }
         identity.groups.put(gid,galias);
      }
      return identity;
      
   }
   
   public static Identity authenticateAgainstService(Context context,Reference service,String username,String password)
      throws RequestFailedException
   {
      Client client = new Client(context,service.getSchemeProtocol());
      client.getContext().getAttributes().put("hostnameVerifier", org.apache.commons.ssl.HostnameVerifier.DEFAULT);
      Request authRequest = new Request(Method.GET,service);
      authRequest.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_BASIC,username,password));
      Response response = client.handle(authRequest);
      if (response.getStatus().isSuccess()) {
         try {
            return createIdentity(response.getEntity());
         } catch (Exception ex) {
            throw new RequestFailedException("Exception while processing result from authentication.",ex);
         }
      } else if (response.getStatus().getCode()==Status.CLIENT_ERROR_UNAUTHORIZED.getCode()) {
         return null;
      } else {
         throw new RequestFailedException("Request to realm failed.",response.getStatus());
      }
   }
   
   public boolean checkPassword(Request request,String identifier,char [] secret)
   {
      getLogger().info("Checking authentication for "+identifier);
      try {
         Identity identity = authenticateAgainstService(getContext().createChildContext(),service,identifier,new String(secret));
         if (identity==null) {
            return false;
         } else {
            request.getAttributes().put(Identity.IDENTITY_ATTR,identity);
            return true;
         }
      } catch (RequestFailedException ex) {
         if (ex.getStatus()!=null) {
            getLogger().severe("Cannot authenticate against service, status="+ex.getStatus().getCode());
         } else {
            getLogger().log(Level.SEVERE,"Cannot authenticate against service.",ex);
         }
         return false;
      }
      
   }
   
   public boolean authorize(Request request) {
      if (roles.size()>0) {
         Identity identity = (Identity)request.getAttributes().get(Identity.IDENTITY_ATTR);
         for (UUID role : roles) {
            if (!identity.hasRole(role)) {
               getLogger().info(identity.getAlias()+" not authorized, missing role "+role);
               request.getAttributes().remove(Identity.IDENTITY_ATTR);
               return false;
            }
         }
         return true;
      } else {
         return true;
      }
   }
   
}
